const cheerio = require('cheerio')

/**
 * * 处理课程周次，将描述周次的文字处理为上课周次的数组
 * @param {string} str eg：'1-12周'  '1-4,7周'  '1-16双周'  '2-10单周'  '5,7,12周'  '1-5,7-9周'  '2-6,8-15双周'  '15单,16双,7-14周'
 * @return {string[]} eg: ['1', '2', '3']
 */
function zc(str) {
  /**
   * 处理单双周
   * @param {string} str_ eg：'1-16双'
   * @param {string} text 需要去除的文字 单 | 双
   * @param {Boolean} double 是否是双周
   * @return {Array<string>} eg: ['1', '2', '3']
   */
  const DS = (str_, text, double) => {
    str_ = str_.replace(text, '')
    if (str_.includes(',') && !str_.includes('-')) { return str_.split(',') }
    if (!str_.includes(',') && !str_.includes('-')) { return [str_] }
    const strat = Number(str_.split('-')[0])
    const end = Number(str_.split('-')[1])
    const need = []
    for (let i = strat; i <= end; i++) {
      if (double) { // 双
        if (i % 2 === 0) { need.push(String(i)) }
      }
      else if (i % 2 !== 0) { need.push(String(i)) } // 单
    }
    return need
  }

  /* 中文逗号、括号转为英文逗号、括号，去掉周字、去掉字符串中的空格、制表符、换行符等空白字符 */
  str = str.replace('(', '').replace('（', '').replace(')', '').replace('）', '').replace(/周/g, '').replace('，', '').trim().replace(/\s/g, '')
  let need = []
  str.split(',').forEach((s) => {
    if (s.includes('单')) {
      need = [...need, ...DS(s, '单', false)]
    }
    else if (s.includes('双')) {
      need = [...need, ...DS(s, '双', true)]
    }
    else if (s.includes('-')) {
      const strat = Number(s.split('-')[0])
      const end = Number(s.split('-')[1])
      for (let i = strat; i <= end; i++) {
        need.push(String(i))
      }
    }
    else need.push(s)
  })
  return need
}

/**
 * 将HTML中的表格转换为列表数据
 * @param {string} html - 包含表格的HTML字符串
 * @param {string} selector - 选择器
 * @param {string} defaultValue - 单元格内容补全时的默认值
 * @param {Function} cb - 单元格内容处理函数，不传默认取文本内容
 * @param {Number} onlyRow - 只取某一行，从0开始
 * @return {Array<any[]>} - 转换后的二维列表数据
 */
// eslint-disable-next-line default-param-last
function tableToList(html, selector, defaultValue = '', cb, onlyRow) {
  try {
    const $ = cheerio.load(html)
    const table = $(selector) // 获取表格元素
    const dataList = [] // 初始化表格数组
    table.find('tr').each((rowIndex, e) => {
      if (onlyRow && rowIndex !== onlyRow) return true

      $(e).find('td, th').each((colIndex, ee) => {
        const cell = $(ee)
        const colspan = Number(cell.attr('colspan')) || 1
        const rowspan = Number(cell.attr('rowspan')) || 1
        let value = cb ? cb(cell, rowIndex, colIndex, $) : cell.text().trim().replace(/\s/g, '') // 获取单元格中内容

        for (let i = 0; i < colspan; i++) { // 处理colspan
          for (let j = 0; j < rowspan; j++) { // 处理rowspan
            const x = rowIndex + j
            let y = colIndex + i
            dataList[x] = dataList[x] || [] // 确保行存在
            while (dataList[x][y] !== undefined) { y++ } // 确保单元格在正确的位置
            dataList[x][y] = value
            value = defaultValue
          }
        }
      })

      return true
    })
    if (onlyRow) return dataList.filter((e) => Boolean(e))
    return dataList
  } catch (err) {
    console.log('err', err)
    throw err
  }
}

/**
 * 根据指定的索引和回调函数，转换输入数组的结构。
 * @param {Array} arr - 需要转换的原始数组。
 * @param {number} index - 在每个子数组中用作新结构键的元素索引。
 * @param {string} cbKey - 可选参数，用于从子数组的指定索引处提取键值的回调函数。
 * @param {string} cbValue - 可选参数，用于从子数组的指定索引处提取值的回调函数。
 * @return {Object} - 返回一个以从指定索引处提取的键值为键，以相应的子数组为值的对象。
 */
function transformArray(arr, index, cbKey, cbValue) {
  // 深拷贝原始数组，以避免修改原始数据
  arr = JSON.parse(JSON.stringify(arr))
  const result = {}
  for (let i = 0; i < arr.length; i++) {
    const innerArr = arr[i]
    if (innerArr === null || innerArr.length === 0) continue
    let keyValue
    // 如果指定索引处的元素是对象，则尝试使用cbKey提取键值，否则使用元素本身
    if (typeof innerArr[index] === 'object' && innerArr[index] !== null) {
      keyValue = JSON.stringify(innerArr[index])
    } else {
      keyValue = innerArr[index]
    }
    keyValue = cbKey ? cbKey(innerArr[index]) : keyValue
    // 如果当前键值在结果对象中不存在，则初始化它为一个空数组,确保结果中每个键对应的值都是一个二维数组
    if (!result[keyValue]) result[keyValue] = []
    // 根据索引位置重新组织子数组，并添加到相应的键值数组中
    // 如果索引为0，则移除第一个元素；否则移除指定索引处的元素
    // 将剩余元素组成的数组添加到对应键的二维数组中
    const values = index === 0 ? innerArr.slice(1) : [...innerArr.slice(0, index), ...innerArr.slice(index + 1)]
    result[keyValue].push(cbValue ? cbValue(values) : values)
  }
  return result
}

/**
 * 根据年份和学期编号计算学期字符串。
 *
 * 该函数旨在根据给定的年份和学期编号，计算出一个表示学期的字符串。
 * 学期字符串的格式为“年份1-年份2-学期类型”，其中年份1和年份2是学期开始的年份，
 * 如果学期是上半年，则学期类型为“1”，如果是下半年，则学期类型为“2”。
 *
 * @param {string} year - 年份字符串。
 * @param {string} sub - 学期编号字符串，从1开始计数。
 * @return {string} 返回计算得到的学期字符串。
 */
const getTerm = (year, sub) => {
  if (!year || !sub || Number.isNaN(Number(year)) || Number.isNaN(Number(sub))) return ''
  // 计算上半年的起始年份
  const t1 = Number(year) + Math.floor((Number(sub) - 1) / 2)
  // 计算下半年的起始年份
  const t2 = Number(year) + Math.floor((Number(sub) - 1) / 2) + 1
  // 根据学期编号的奇偶性决定学期类型是“1”还是“2”
  return `${t1}-${t2}-${Number(sub) % 2 === 0 ? '2' : '1'}`
}

module.exports = {
  zc,
  tableToList,
  transformArray,
  getTerm,
}
